import os

from typing_extensions import Union, List
from fastapi import Request, UploadFile
from sqlalchemy import select, delete

from core.database import db_session
from core.models import Board, WriteBaseModel, Group, AutoSave
from core.exception import AlertException
from core.formclass import WriteForm
from lib.board_lib import (
    BoardConfig, FileCache, is_owner, is_write_delay, send_write_mail
)
from lib.member import MemberDetails
from lib.common import (
    dynamic_create_write_table, filter_words,
    remove_query_params, set_url_query_params
)
from lib.html_sanitizer import content_sanitizer
from lib.slowapi.create_post_limit.limiter import validate_slowapi_create_post
from lib.pbkdf2 import create_hash, validate_password
from service import BaseService
from service.board_file_service import BoardFileService
from api.v1.models.board import WriteModel
from api.v1.service.member import MemberImageServiceAPI as ImageService


class BoardService(BaseService, BoardConfig):
    """基于公告栏的服务类别
"""

    FILE_DIRECTORY = "data/file/"

    def __init__(
        self,
        request: Request,
        db: db_session,
        bo_table: str,
    ):
        self.db = db
        board = self.get_board(bo_table)  #install 项目时已经包含了一些board table数据，在函数 instal/router/board_setup 中；
        super().__init__(request, board)
        self.bo_table = bo_table
        self.write_model = dynamic_create_write_table(bo_table)
        self.categories = self.get_category_list()
        self.member = MemberDetails(request, request.state.login_member, board=self.board)

    def raise_exception(self, status_code: int, detail: str = None, url: str = None):
        raise AlertException(status_code=status_code, detail=detail, url=url)

    def validate_wr_password(self, wr_password: str = None):
        """验证非会员写作时是否填写密码"""
        if self.member.mb_id:
            return
        if not wr_password:
            self.raise_exception(detail="编写非成员时，必须包含密码.", status_code=400)

    def set_wr_name(self, member: MemberDetails = None, default_name: str = None) -> str:
        """确认是否使用实名后，如果是实名，将返还姓名或昵称。

Args:
board：公告板object
member（Member Details）：会员信息object

Returns:
str:姓名或昵称
        """
        if member.mb_id:
            if self.board.bo_use_name:
                return member.mb_name
            else:
                return member.mb_nick
        elif default_name:
            return default_name
        else:
            self.raise_exception(detail="登录会话的过期时间, 非会员写作是一种不典型的方法，如作者的名字没有列出.", status_code=400)

    def validate_admin_authority(self):
        """验证公告栏管理员"""
        if not self.member.admin_type:
            self.raise_exception(detail="您可以访问多个公告板管理器.", status_code=403)

    def validate_write_level(self):
        """写作水平的可比验证“验证写作水平比较”"""
        if not self.is_write_level():
            self.raise_exception(detail="你无权写.", status_code=403)

    def validate_author(self, write: WriteBaseModel, wr_password: str = None):
        """作者的确认"""
        if self.member.admin_type:
            return

        if not is_owner(write, self.member.mb_id) and not validate_password(wr_password, write.wr_password):
            self.raise_exception(detail="仅作者更正/可以删除.", status_code=403)

    def validate_secret_board(self, secret: str, html: str, mail: str):
        """验证公告栏是否使用秘密文章"""
        if self.member.admin_type:
            return

        # 检查是否使用秘密文章
        if not self.board.bo_use_secret and "secret" in secret and "secret" in html and "secret" in mail:
            self.raise_exception(status_code=403, detail="因为是未使用秘密文章的留言板，所以无法注册为秘密文章。")

        # 根据秘密文章选项设置秘密文章
        if self.board.bo_use_secret == 2:
            self.secret = "secret"

    def validate_post_content(self, content):
        """验证帖子的内容"""
        filtered_word = filter_words(self.request, content)
        if filtered_word:
            self.raise_exception(detail=f"内容上禁止使用的单词({filtered_word})包括.", status_code=400)

    def validate_write_delay(self):
        """验证写作间隔"""
        if not is_write_delay(self.request):
            self.raise_exception(status_code=400, detail="您不能在太快的时间内连续发布帖子.")

    def validate_write_delay_with_slowapi(self): #这个函数仅仅是测试；
        """写作间隔验证（slowapi）"""
        validate_slowapi_create_post(self.request)

    def validate_anonymous_password(self, data):
        """写入非成员时验证密码"""
        if not self.member and not data.wr_password:
            self.raise_exception(detail="编写非成员时，必须包含密码.", status_code=400)

    def get_member_image_path(self, mb_id: str):
        """会员图片查询"""
        return ImageService.get_image_path(mb_id)

    def get_member_icon_path(self, mb_id: str):
        """会员图标查询"""
        return ImageService.get_icon_path(mb_id)

    def get_board(self, bo_table: str) -> Board:  #install 项目时已经包含了一些board table数据，在函数 instal/router/board_setup 中；
        """获取公告栏信息."""
        board = self.db.get(Board, bo_table) #这里是根据主键查询的，所以可以不用展示字段
        if not board:
            self.raise_exception(detail="这是一个不存在的公告板.", status_code=404)
        return board

    def get_admin_board_list(self) -> List[Board]:
        """获取经理所属的公告板列表."""
        query = select(Board).join(Group).order_by(Board.gr_id, Board.bo_order, Board.bo_table)
        if self.member.admin_type == "group":
            query = query.where(Group.gr_admin == self.member.mb_id)
        elif self.member.admin_type == "board":
            query = query.where(Board.bo_admin == self.member.mb_id)
        return self.db.scalars(query).all()

    def get_write(self, wr_id: Union[int, str]) -> WriteBaseModel:
        """post(评论)带来."""
        if not isinstance(wr_id, int) and not wr_id.isdigit():
            self.raise_exception(detail="这是一个不正确的帖子编号.", status_code=400)

        write = self.db.get(self.write_model, wr_id)
        if not write:
            self.raise_exception(detail="不存在的帖子(评论)就这样..", status_code=404)

        # 成员图像, 图标路径设置
        write.mb_image_path = self.get_member_image_path(write.mb_id)
        write.mb_icon_path = self.get_member_icon_path(write.mb_id)

        return write

    def get_board_per_page(self, per_page = 0):
        """获取每页的帖子辨别数。"""
        if per_page != 0:
            return per_page
        if self.board.bo_page_rows != 0:
            return self.board.bo_page_rows  # 公告栏每页的帖子数
        else:
            return self.config.cf_page_rows # 使用在父类（BoardConfig）中设置的每页帖子数

    def get_parent_post(self, parent_id: int, is_reply: bool = True):
        """调用父项"""
        if not parent_id:
            return None

        if is_reply:    # 答案写作
            validate_func = self.is_reply_level
            target_expr = "答案写作"
        else:           # 评论
            validate_func = self.is_comment_level
            target_expr = "评论"

        # 答案/评论权限验证
        if not validate_func():
            self.raise_exception(detail=f"{target_expr}你无权写.", status_code=403)

        # 父级书写调用
        parent_write = self.db.get(self.write_model, parent_id)
        if not parent_write:
            self.raise_exception(f"{target_expr}没有原创的写作可写.", 404)
        return parent_write

    def get_redirect_url(self, write):
        """重定向 URL 创建"""
        query_params = remove_query_params(self.request, "parent_id")
        url = f"/board/{self.bo_table}/{write.wr_id}"
        return set_url_query_params(url, query_params)

    def get_cleaned_data(self, content):
        """“用于防止存储XSS的数据清理”"""
        return content_sanitizer.get_cleaned_data(content)

    def arrange_data(
        self,
        data: Union[WriteForm, WriteModel],
        secret: str,
        html: str,
        mail: str,
        is_update: bool =False
    ):
        """
        根据表单整理以form或body形式输入的数据
          - 项目: ca_name, wr_password, wr_name, wr_email, wr_homepage, wr_option, wr_link1, wr_link2, wr_content
        """
        category_list = self.board.bo_category_list.split("|") if self.board.bo_category_list else []
        if category_list:
            if not data.ca_name or data.ca_name not in category_list:
                self.raise_exception(
                    status_code=400,
                    detail=f"ca_name: {data.ca_name}, 这是一个错误的分类。. 分类是 {','.join(category_list)} 它必须是一个."
                )
        else:
            data.ca_name = ""
        self.validate_wr_password(data.wr_password)
        data.wr_password = create_hash(data.wr_password) if data.wr_password else ""
        if not is_update:
            data.wr_name = self.set_wr_name(self.member, data.wr_name)
        data.wr_email = getattr(self.member, "mb_email", data.wr_email)
        data.wr_homepage = getattr(self.member, "mb_homepage", data.wr_homepage)

        #设置选项
        options = [opt for opt in [html, secret, mail] if opt]
        data.wr_option = ",".join(map(str, options))

        # 链接设置
        if not self.is_link_level():
            data.wr_link1 = ""
            data.wr_link2 = ""

        # Stored XSS保护
        data.wr_content = self.get_cleaned_data(data.wr_content)

    def set_notice(self, wr_id: int, notice: bool):
        # 设置公告
        self.board.bo_notice = self.set_board_notice(wr_id, notice)

    def send_write_mail_(self, write, parent_write):
        """发送邮件"""
        if self.use_email:
            send_write_mail(self.request, self.board, write, parent_write)

    def save_secret_session(self, wr_id: int, secret: str):
        """一个会话 request保存在"""
        if secret == "secret":
            self.request.session[f"ss_secret_{self.bo_table}_{wr_id}"] = True

    def delete_cache(self):
        """ 删除最新文章缓存"""
        FileCache().delete_prefix(f"latest-{self.bo_table}")
    
    def delete_auto_save(self, uid: str):
        """删除自动存储"""
        if uid:
            self.db.execute(delete(AutoSave).where(AutoSave.as_uid == uid))

    def upload_files(
        self,
        file_service: BoardFileService,
        write: WriteBaseModel,
        file_list: List[UploadFile],
        file_content: List[str] = None,
        file_dels: list = None,
        password_verified: bool = False
    ):
        # “上传文件”
     # files = []
        # print("file_list", file_list)
        # for file in file_list:
        #     if getattr(file, "size", None):
        #         files.append(file)
# 此时，is_not_same_mb_id 将为 True，因为 self.member.mb_id 存在且不等于 write.mb_id。

# 如果 self.member.mb_id 是 None 或者 ""（空字符串），is_not_same_mb_id 将为 False。
        is_not_same_mb_id = self.member.mb_id and self.member.mb_id != write.mb_id

        if not self.member.admin_type and is_not_same_mb_id and not password_verified:
            self.raise_exception(status_code=403, detail="您只能将文件上传到您自己的帖子.")

        if not self.is_upload_level():
            self.raise_exception(status_code=403, detail="您没有上传文件的权限.")

        directory = os.path.join(self.FILE_DIRECTORY, self.bo_table)
        wr_file = write.wr_file

        # 路径创建
        os.makedirs(directory, exist_ok=True)

        # 删除文件
        # 파일 삭제
        if file_dels:
            for bf_no in file_dels:
                file_service.delete_board_file(self.board.bo_table, write.wr_id, bf_no)
                wr_file -= 1

        # 文件上传处理和文件信息存储
        exclude_file = {"size": [], "ext": []}
        for file in file_list:
            index = file_list.index(file)

            if file and file.filename:
                # 如果您不是管理员，则不会上载，除非它大于您设置的上载大小，或者不是上载扩展
                if not self.member.admin_type:
                    if not file_service.is_upload_size(self.board, file):
                        exclude_file["size"].append(file.filename)
                        continue
                    if not file_service.is_upload_extension(file):
                        exclude_file["ext"].append(file.filename)
                        continue

                board_file = file_service.get_board_file(self.board.bo_table, write.wr_id, index)
                bf_content = file_content[index] if file_content and file_content[index] else ""
                filename = file_service.get_filename(file.filename)
                if board_file:
                    # 删除现有文件
                    file_service.remove_file(board_file.bf_file)
                    # 文件上传和信息更新
                    file_service.upload_file(directory, filename, file)
                    file_service.update_board_file(board_file, directory, filename, file, bf_content)
                else:
                    # 文件上传和添加信息
                    file_service.upload_file(directory, filename, file)
                    file_service.insert_board_file(self.board.bo_table, write.wr_id, index,
                                                        directory, filename, file, bf_content)
                    wr_file += 1

        # exclude_file文件上传失败消息输出 (如果存在)
        msg = ""
        if exclude_file.get("size"):
            msg += f"{','.join(exclude_file['size'])} 文件上传容量({self.board.bo_upload_size}byte)超过.\\n"
        if exclude_file.get("ext"):
            msg += f"{','.join(exclude_file['ext'])} 文件不是可上载的扩展名.\\n"
        if msg:
            self.raise_exception(detail=msg, status_code=400)

        # 更新文件数
        write.wr_file = wr_file
        self.db.commit()
